package controller;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MonteCarloSimulation {
	public static double MonteCarloStandardOption(String CallPutFlag, double S, double X,
			double T, double r, double b, double v, int nSteps, int nSimulations) {
		//
		double dt, St, Sum, Drift, vSqrdt;
		int i, j, z;
		//
		Sum = 0;
		z = 0;
		//
		dt = T / nSteps;
		Drift = (b - (Math.pow(v, 2) / 2)) * dt;
		vSqrdt = v * Math.sqrt(dt);
		//
		if (CallPutFlag.equals("c"))
			z = 1;
		else if (CallPutFlag.equals("p"))
			z = -1;
		//
		for (i = 1; i <= nSimulations; i++) {
			St = S;
			for (j = 1; j <= nSteps; j++) {
				St = St * Math.exp(Drift + vSqrdt * (new Random().nextGaussian()));
			}
			Sum = Sum + Math.max(z * (St - X), 0);
		}
		
		return (Math.exp(-r * T) * (Sum / nSimulations));
	}
	
	
	public static double MonteCarloMultiThreads(String CallPutFlag, final double S, double X,
			double T, double r, double b, double v, final int nSteps, int nSimulations) throws InterruptedException, ExecutionException {
		//
		double dt, St, Sum;
		final double Drift;
		final double vSqrdt;
		double blockingCoefficient;
		int i;
		int z;
		//
		Sum = 0;
		z = 0;
		blockingCoefficient = 0.9;
		//
		dt = T / nSteps;
		Drift = (b - (Math.pow(v, 2) / 2)) * dt;
		vSqrdt = v * Math.sqrt(dt);
		//
		if (CallPutFlag.equals("c"))
			z = 1;
		else if (CallPutFlag.equals("p"))
			z = -1;
		
		final int numberOfCores = Runtime.getRuntime().availableProcessors();
		final int poolSize = (int)(numberOfCores / (1 - blockingCoefficient));
		
		final List<Callable<Double>> partitions = new ArrayList<Callable<Double>>();
		for (i=0 ; i<nSimulations ; i++) {
			partitions.add(new Callable<Double>() {
		      public Double call() throws Exception {
		    	  double St = S;
		    	  Random rand = new Random();
		    	  for (int j=0 ; j<nSteps ; j++) {
					St *= Math.exp(Drift + vSqrdt * rand.nextGaussian());
		    	  }
		    	  return St;
		      }
		    });
		}
		
		final ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);    
	    final List<Future<Double>> valueOfCalculations = executorPool.invokeAll(partitions);
	    executorPool.shutdown();
	    
	    for(final Future<Double> valueOfACalcul : valueOfCalculations) 
	    	Sum += Math.max(z * (valueOfACalcul.get() - X), 0);
	    
	    return Math.exp(-r * T) * (Sum / nSimulations);
	}
}
